#!/bin/bash
#
# Copyright 2015-2017 Adrian DC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# === Standalone Source Helper ===
# source <(curl -Ls https://github.com/AdrianDC/android_development_shell_tools/raw/master/android_host_common.rc)
# source <(curl -Ls https://github.com/AdrianDC/android_development_shell_tools/raw/master/android_repo_cleaners.rc)
# source <(curl -Ls https://github.com/AdrianDC/android_development_shell_tools/raw/master/android_repo_helpers.rc)

# === Android gettop ===
function gettop()
{
  # Usage: gettop (Get repo root path)

  # Variables
  local CURDIR;
  local TOP;

  # Initialize variables
  CURDIR=${PWD};
  TOP=${CURDIR};

  # Access parent until found or root
  while [ ! -d '.repo' ] && [ ! "${PWD}" = '/' ]; do
    cd ../;
    TOP=$(PWD='' /bin/pwd -P);
  done;

  # Echo if path is valid
  cd "${CURDIR}";
  if [ -d "${TOP}/.repo" ]; then
    echo "${TOP}";
  fi;
}

# === Android croot ===
function croot()
{
  # Usage: croot (Access repo root path)

  # Variables
  local TOP;

  # Initialize variables
  TOP=$(gettop);

  # Access if valid
  if [ "${TOP}" ]; then
    cd "$(gettop)";
  else
    echo "Couldn't locate the top of the tree.  Try setting TOP.";
  fi;
}

# === Repo Environment Starter for LineageOS ===
function repos()
{
  # Usage
  if [ -z "${1}" ]; then
    echo '';
    echo ' Usage: repos <device_name> (Prepare LineageOS device environment)';
    echo '';
    return;
  fi;

  # Variables
  local currentdir=${PWD};
  local device=${1};
  local target;

  # Access repo root path
  croot >/dev/null;

  # Validate envsetup exists
  if [ ! -f ./build/envsetup.sh ]; then
    echo '';
    echo -e ' \e[1;31mError: Android Repo not found...\e[0m';
    echo '';
    cd "${currentdir}";
    return;
  fi;

  # Override OUT for symlinks
  if [ -L './out' ]; then
    export OUT_DIR=$(readlink -f './out');
  else
    export OUT_DIR='';
  fi;

  # Unset CM/Lineage functions and load envsetup
  unset -f breakfast;
  unset -f brunch;
  source ./build/envsetup.sh; croot;

  # Prepare device environment for LineageOS
  if type breakfast >/dev/null 2>&1; then
    breakfast "${device}";

  # Prepare device environment for AOSP
  else
    target=$(codenametotarget "${device}");
    lunch "aosp_${target}-userdebug";
  fi;

  # Return to current folder
  cd "${currentdir}";
}

# === Roomservice Manifest ===
function reporoomserv()
{
  # Usage: reporoomserv (Manifest and local_manifests editor)

  # Variables
  local cwd;
  local files;

  # Path handling
  cwd=$(pwd);
  croot;

  # Folder creation
  mkdir -p .repo/local_manifests/;

  # Files to open
  files=('.repo/local_manifests/roomservice.xml' '.repo/manifest.xml');
  if [ -f .repo/local_manifests/untracked.xml ]; then
    files+=('.repo/local_manifests/untracked.xml');
  fi;

  # Manifest and local_manifests editor
  fileedit "${files[@]}";

  # Restore path
  cd "${cwd}";
}

# === Repo Sync Efficiently ===
function reposy()
{
  # Usage: reposy (Optimized relevant repo sync)

  # Optimized relevant repo sync
  repo sync -j"$(grep -c ^processor /proc/cpuinfo)" \
            --current-branch \
            --detach \
            -f \
            --force-broken \
            --force-sync \
            -c \
            --no-clone-bundle\
            --no-tags \
            "${@}";
}

# === Repo Branch Sync with Safeguard ===
function reposysafe()
{
  # Usage: reposysafe (Safeguarded repo projects sync)

  # Variables
  local shtools_path;

  # Load shtools_path
  shtools_path=$(shtoolsget);

  # Safeguarded repo projects sync
  echo '';
  # shellcheck disable=SC2016
  repo forall -j"$(grep -c ^processor /proc/cpuinfo)" -c "
        is_git_valid=;
        git rev-parse --git-dir
        if git rev-parse --git-dir > /dev/null 2>&1; then
          is_git_valid=true;
        fi;
        if [ -z \"\${is_git_valid}\" ]; then
          source \"${shtools_path}/android_repo_cleaners.rc\";
          source \"${shtools_path}/android_repo_helpers.rc\";
          cd ./$(echo \"\$\{REPO_PATH\}\" | sed 's/[^\/]*/../g');
          rm -rf \"\$\{REPO_PATH\}\";
          echo -e \"\n \e[1;32mStarting: Project \${REPO_PROJECT} : \${REPO_REMOTE}/\${REPO_RREV##*/}\e[0m\n\";
          reposy \"\$\{REPO_PATH\}\";

        else
          false;
          while [ \${?} -ne 0 ]; do
            echo -e \"\n \e[1;33mFetch: Project \${REPO_PROJECT} : \${REPO_REMOTE}/\${REPO_RREV##*/}\e[0m\n\";
            timeout 20 git fetch --progress \"\${REPO_REMOTE}\" \"\${REPO_RREV##*/}\";
          done;
          if git add -N ./* && ! git diff-index --quiet HEAD -- || git am --abort >/dev/null 2>&1; then
            echo -e \"\n \e[1;32mCleaning: Project \${REPO_PROJECT} : \${REPO_REMOTE}/\${REPO_RREV##*/}\e[0m\n\";
            git reset HEAD --hard;
            git stash -u;
            git am --abort;
          fi;
          echo -e \"\n \e[1;32mCheckout: Project \${REPO_PROJECT} : \${REPO_REMOTE}/\${REPO_RREV##*/}\e[0m\n\";
          if git rev-parse --git-dir > /dev/null 2>&1 || ! git checkout \"\${REPO_RREV##*/}\"; then
            source \"${shtools_path}/android_repo_cleaners.rc\";
            source \"${shtools_path}/android_repo_helpers.rc\";
            echo -e \"\n \e[1;32mRestarting: Project \${REPO_PROJECT} : \${REPO_REMOTE}/\${REPO_RREV##*/}\e[0m\n\";
            cd ./$(echo \"\$\{REPO_PATH\}\" | sed 's/[^\/]*/../g');
            reposyrm \"\${REPO_PATH}\";
          fi;
        fi;
        ";
  echo '';
}

# === Repo Branch Sync ===
function reposybranch()
{
  # Usage: reposybranch (Individual repo projects sync)

  # Individual repo projects sync
  echo '';
  # shellcheck disable=SC2016
  repo forall -c 'echo "";
                  echo " Project ${REPO_PROJECT} : ${REPO_REMOTE}/${REPO_RREV##*/}";
                  git fetch ${REPO_REMOTE} ${REPO_RREV##*/};
                  git checkout ${REPO_RREV##*/};';
  echo '';
}

# === Repo Prune ===
function repoprune()
{
  # Usage: repoprune (Apply repo-wide prune cleanup)

  # Apply repo-wide prune cleanup
  echo '';
  # shellcheck disable=SC2016
  repo forall -c 'tmpfile=$(mktemp);
                  echo "";
                  echo " Project ${REPO_PROJECT} : ${REPO_REMOTE}/${REPO_RREV##*/}\";
                  git prune HEAD 2> "${tmpfile}";
                  while read -r line; do
                    line=refs${line#*refs};
                    echo "  Removing ${line}";
                    rm -rf .git/\${line};
                  done < "${tmpfile}";
                  rm -f "${tmpfile}";
                  return;';
  echo '';
}

# === Repo Sync Clean ===
function reposycl()
{
  # Usage: reposycl (Cleaned optimized relevant repo sync)

  # Cleaned optimized relevant repo sync
  # shellcheck disable=SC2016
  repo forall -c 'echo "Cleaning project ${REPO_PROJECT}";
                  git rebase --abort > /dev/null 2>&1;
                  git stash -u > /dev/null 2>&1;
                  git reset --hard HEAD > /dev/null 2>&1;';
  repo sync -j"$(grep -c ^processor /proc/cpuinfo)" --current-branch --detach -f --force-broken --force-sync -c --no-clone-bundle --no-tags;
}

# === Repo CCache Status/Setter ===
function repocache()
{
  # Usage: repocache [cache_maximum_size] (CCache watcher and configuration)

  # Variables
  local currentdir;

  # Access repo root
  currentdir=$(pwd);
  croot;

  # Update the maximum on input (example 20G)
  if [ ! -z "${1}" ]; then
    ./prebuilts/misc/linux-x86/ccache/ccache -M "${1}";
  fi;

  # Study the CCache
  watch -n 1 -d "./prebuilts/misc/linux-x86/ccache/ccache" -s;

  # Restore path
  cd "${currentdir}/";
}

# === Repo Project Branch ===
function repogetbranch()
{
  # Usage: repogetbranch (Get repo project branch)

  # Variables
  local branch='';
  local infos;

  # Initialize variables
  infos=$(repo info . 2>&1);

  # Repo detection
  if [[ ! "${infos}" == *'repo to be installed'* ]] && [[ ! "${infos}" == *'sudo apt'* ]]; then
    branch=$(echo "${infos}" \
          | grep -i 'Current revision:' \
          | head -n 1 \
          | sed 's/.*: \(refs\/heads\/\|refs\/tags\/\|\)\(.*\)/\2/');
  fi;

  # Recent remotes
  if [ -z "${branch}" ] || [[ "${branch}" == *'Manifest'* ]]; then
    branch=$(git 'for-each-ref' --sort=committerdate refs/remotes/ \
           | grep "$(githubusername)\|origin\|xperia" \
           | head -n 1 \
           | sed 's/.*remotes\/[^\/]*\/\(.*\)/\1/');
  fi;

  # Detect branch from refs
  if [ -z "${branch}" ] || [[ "${branch}" == *'Manifest'* ]] || [[ "${branch}" == *'HEAD'* ]]; then
    branch=$(gitgetbranch);
  fi;

  # Default fallback
  if [ -z "${branch}" ] || [[ "${branch}" == *'Manifest'* ]] || [[ "${branch}" == *'HEAD'* ]]; then
    branch=lineage-15.0;
  fi;

  # Output result
  echo "${branch}";
}

# === Repo List Exclude Search ===
function repolistexclude()
{
  # Usage: repolistexclude [word_to_search] (Get repo list fields to exclude with search)

  # List all fields
  echo '';
  if [ -z "${1}" ]; then
    repo list | sed 's/\(.*\) : \(.*\)/\2 [\1]/g' \
              | sort;

  # Get repo list fields to exclude with search
  else
    repo list | grep "${1}" \
              | sed 's/.* : \(.*\)/  <remove-project name="\1" \/>/g' \
              | sort;
  fi;
  echo '';
}

# === Repo Switcher ===
function reposwitcher()
{
  # Usage: reposwitcher [bool_init] (Helper to switch between local_manifests_* folders)

  # Variables
  local cnt;
  local folder;
  local key;
  local options;
  local out;
  local path;
  local path_current='';
  local reposy_all=();

  # Access Android root
  path=$(pwd);
  croot;

  # Initialization mode
  if [ ! -z "${1}" ]; then

    # Remove leftover symlinks or regular folders
    if [ -L '.repo/local_manifests' ]; then
      rm .repo/local_manifests;
    else
      rm -rf .repo/local_manifests;
    fi;
    if [ -L 'out' ]; then
      rm out;
    else
      rm -rf out;
    fi;

    # Request user input for local_manifests_<name>
    echo '';
    echo -en  " \e[1;33mreposwitcher: Name for local_manifests_<name> :\e[0m ";
    read -r key;
    echo '';

    # Create folder, generate symlink to local_manifests folder
    folder="local_manifests_${key}";
    mkdir -p ".repo/${folder}";
    echo -n ' ';
    rm -fv .repo/local_manifests;
    ln -fsv "${folder}" .repo/local_manifests;

    # Populate local_manifests linkages
    reporeflinker;

  fi;

  # Abort if local_manifests is not a symlink
  if [ -e '.repo/local_manifests' ] && [ ! -L '.repo/local_manifests' ]; then
    echo '';
    echo ' reposwitcher: local_manifests is not a symlink';
    echo '   Please delete the ".repo/local_manifests" folder';
    echo '   Add ".repo/local_manifests_NAME" folders';
    echo '';
    return;
  fi;

  # Abort if out is not a symlink
  if [ -e 'out' ] && [ ! -L 'out' ]; then
    echo '';
    echo ' reposwitcher: out is not a symlink';
    echo '   Please delete the "out" folder';
    echo '';
    return;
  fi;

  # Detect current local_manifests symlink
  if [ -L '.repo/local_manifests' ]; then
    path_current=$(readlink -f '.repo/local_manifests');
    path_current=${path_current##*/};
  fi;

  # Create folders selector list
  cnt=0;
  options=('Keep current local_manifests');
  folder=;
  echo '';
  echo -e " \e[1;37m[ Select a folder for local_manifests ]\e[0m";
  echo '';
  echo "   ${cnt}: ${options[${cnt}]}";
  for folder in .repo/local_manifests_*; do
    cnt=$((cnt + 1));
    options[${cnt}]=${folder##*/};
    if [ "${options[${cnt}]}" = "${path_current}" ]; then
      echo -e "   ${cnt}: ${options[${cnt}]} \e[1;37m(Active)\e[0m";
    else
      echo "   ${cnt}: ${options[${cnt}]}";
    fi;
  done;
  echo '';
  echo -en  " \e[1;33mlocal_manifests to use [0 to ${cnt}] :\e[0m ";
  read -r key;
  echo '';
  if [ ! -z "${key}" ] && [ "${key}" -gt 0 ] && [ "${key}" -le ${cnt} ]; then
    folder=${options[${key}]};
  else
    return 0;
  fi;

  # If folder provided
  if [ ! -z "${folder}" ]; then

    # Generate symlink to local_manifests folder
    echo -n ' ';
    rm -fv .repo/local_manifests;
    ln -fsv "${folder}" .repo/local_manifests;
    echo '';

    # Generate out specific folder
    out=out_${folder#local_manifests_};
    if [ ! -d "${out}" ]; then
      mkdir "${out}";
    fi;

    # Generate symlink to out folder
    echo -n ' ';
    rm -fv out;
    ln -fsv "${out}" out;
    echo '';

  # Delete otherwise
  else
    rm -f .repo/local_manifests;
  fi;

  # Launch repo init menu
  repoinit;

  # Launch repo syncer
  reposy "${reposy_all[@]}";

  # Return to current path
  cd "${path}";
}
